#ifndef NO_GRAPHIC
#include "graphic.h"
#include "cell.h"
#include "cell_func.h"
#include "defines.h"
#include <stdio.h>
#include <limits.h>

extern char filepath[];
struct _Button buttons[MAX_BUTTONS_COUNT];

Button registerButton(struct Graphic *g, char text[], SDL_Color color, ButtonFunc pressFunc) {
    for(unsigned i = 0; i < MAX_BUTTONS_COUNT; i++) {
        if(!buttons[i].active) {
            buttons[i].active = true;
            memcpy(&(buttons[i].color), &color, sizeof(SDL_Color));
            strncpy(buttons[i].text, text, 9);
            buttons[i].func = pressFunc;
            return i;
        }
    }
    // Yes
    return (unsigned)(-1);
}

bool checkButton(struct Graphic *g, Button button) {
    int buttonY = buttonToScreenY(button);
    return g->mouseX2 > BUTTON_SPACE &&
           g->mouseY2 > BUTTON_SPACE+buttonY &&
           g->mouseX2 < BUTTON_SPACE+BUTTON_WIDTH &&
           g->mouseY2 < BUTTON_SPACE+buttonY+BUTTON_HEIGHT;
}

int buttonToScreenY(Button button) {
    return button * BUTTON_HEIGHT + button * BUTTON_SPACE;
}

struct Graphic *G_init(unsigned width, unsigned height, char* fontpath) {
    struct Graphic *g = (struct Graphic*)malloc(sizeof(struct Graphic));
    if(SDL_Init(SDL_INIT_VIDEO))
        printf("%s\n", SDL_GetError());
    if(TTF_Init())
        printf("%s\n", SDL_GetError());
    g->font = TTF_OpenFont(fontpath, 25);
    if(g->font == NULL)
        printf("%s\n", TTF_GetError());
    SDL_ShowCursor(SDL_DISABLE);
    g->zoom = PIXEL_SIZE;
    g->width = width;
    g->height = height;
    int barWidth = PIXEL_SIZE*8;
    g->viewport = (SDL_Rect){.x = barWidth, .y = 0, .w = g->width-barWidth, .h = g->height-PIXEL_SIZE};
    g->mouseFunc[1] = DM_WALL;
    g->mouseFunc[2] = DM_INFO;
    g->mouseFunc[3] = DM_DEL;
    SDL_CreateWindowAndRenderer(width, height, 0, &g->window, &g->renderer);
    SDL_SetWindowTitle(g->window, "Cells simulation");
    SDL_SetWindowResizable(g->window, true);
    return(g);
}

void G_close(struct Graphic *g) {
    TTF_CloseFont(g->font);
    TTF_Quit();
    SDL_Quit();
    SDL_DestroyRenderer(g->renderer);
    SDL_DestroyWindow(g->window);
    SDL_ShowCursor(SDL_ENABLE);
}

void drawText(struct Graphic *g, char* text, SDL_Color color, int x, int y) {
    SDL_Surface *surface = TTF_RenderText_Solid(g->font, text, color);
    SDL_Texture *texture = SDL_CreateTextureFromSurface(g->renderer, surface);
    int textW, textH;
    SDL_QueryTexture(texture, NULL, NULL, &textW, &textH);
    SDL_Rect rect = (SDL_Rect){
        .x = x - textW/2,
        .y = y - textH/2,
        .w = textW,
        .h = textH,
    };
    SDL_RenderCopy(g->renderer, texture, NULL, &rect);
    SDL_DestroyTexture(texture);
    SDL_FreeSurface(surface);
}

void render(struct World *w, struct Graphic *graphic) {
    SDL_SetRenderDrawColor(graphic->renderer, 30, 30, 46, 0);
    SDL_RenderClear(graphic->renderer);
    SDL_SetRenderDrawColor(graphic->renderer, 24, 24, 37, 0);
    SDL_RenderFillRect(graphic->renderer, &graphic->viewport);
    int maxenergy=0, maxAge = 0;
    for(int y=0; y < w->height; y++) {
        for(int x=0; x < w->width; x++) {
            struct Cell cell =  { 0 };
            memcpy(&cell, &w->cells[cordsToIndex(w, x, y)], sizeof(struct Cell));
            SDL_Rect rect;
            int r=0, g=0, b=0;
            int maxval=0;
            int rx = 0, ry = 0;
            worldToScreen(graphic, (float)x, (float)y, &rx, &ry);
            rect.x = rx;
            rect.y = ry;
            rect.w = graphic->zoom+1;
            rect.h = graphic->zoom+1;
            if(rx < graphic->viewport.x || rx > graphic->viewport.x + graphic->viewport.w ||
               ry < graphic->viewport.y || ry > graphic->viewport.y + graphic->viewport.h) continue;
            SDL_SetRenderDrawColor(graphic->renderer, 0, 0, 0, 0);
            SDL_RenderFillRect(graphic->renderer, &rect);
            switch(cell.type) {
            case(T_CELL):
                switch (graphic->colorType) {
                case(0):
                    r = cell.color.r;
                    g = cell.color.g;
                    b = cell.color.b;
                    break;
                case(1):
                    if(cell.attackCount > maxval)
                        maxval = cell.attackCount;
                    if(cell.mineralsCount > maxval)
                        maxval = cell.mineralsCount;
                    if(cell.photosynthesisCount > maxval)
                        maxval = cell.photosynthesisCount;
                    r = ((float)cell.attackCount         / (float)maxval) * 255;
                    g = ((float)cell.photosynthesisCount / (float)maxval) * 255;
                    b = ((float)cell.mineralsCount       / (float)maxval) * 255;
                    break;
                case(2):
                    if(w->maxEnergy == 0) {
                        for(int ly=0; ly<w->height; ly++) {
                            for(int lx=0; lx<w->width; lx++) {
                                if(w->cells[cordsToIndex(w, lx, ly)].energy > maxenergy) {
                                    maxenergy = w->cells[cordsToIndex(w, lx, ly)].energy;
                                }
                            }
                        }
                    } else {
                        maxenergy = w->maxEnergy;
                    }
                    int energy = ((float)cell.energy / (float)maxenergy) * 255;
                    r = 128;
                    g = energy;
                    b = 0;
                    break;
                case(3):
                    if(w->maxAge == 0) {
                        for(int ly=0; ly<w->height; ly++) {
                            for(int lx=0; lx<w->width; lx++) {
                                if(w->cells[cordsToIndex(w, lx, ly)].age > maxAge) {
                                    maxAge = w->cells[cordsToIndex(w, lx, ly)].age;
                                }
                            }
                        }
                    } else {
                        maxAge = w->maxAge;
                    }
                    g = ((float)cell.age / (float)maxAge) * 255;
                    r = 64;
                    b = 64;
                    break;
                case(C_END):
                    break;
                }
                SDL_SetRenderDrawColor(graphic->renderer, r, g, b, 0);
                rect.x = rx;
                rect.y = ry;
                SDL_RenderFillRect(graphic->renderer, &rect);
                rx = 0, ry = 0;
                if(graphic->zoom != 1) {
                    getRotation(cell.rotation, &rx, &ry);
                    SDL_SetRenderDrawColor(graphic->renderer, 255-r, 255-g, 255-b, 0);
                    int trx = (((graphic->zoom/2)*rx)+(graphic->zoom/2));
                    int try = (((graphic->zoom/2)*ry)+(graphic->zoom/2));
                    SDL_RenderDrawLine(graphic->renderer, (rect.w/2)+rect.x, (rect.h/2)+rect.y, rect.x+trx, rect.y+try);
                }
                break;
            case(T_FOOD):
                SDL_SetRenderDrawColor(graphic->renderer, 200, 200, 0, 0);
                rect.x = rx+(graphic->zoom/4)+1;
                rect.y = ry+(graphic->zoom/4)+1;
                rect.w = graphic->zoom-(graphic->zoom/2)+1;
                rect.h = graphic->zoom-(graphic->zoom/2)+1;
                SDL_RenderDrawRect(graphic->renderer, &rect);
                break;
            case(T_WALL):
                SDL_SetRenderDrawColor(graphic->renderer, 128, 128, 128, 0);
                SDL_RenderFillRect(graphic->renderer, &rect);
                break;
            case(T_MINWALL):
                SDL_SetRenderDrawColor(graphic->renderer, 240, 240, 240, 0);
                SDL_RenderDrawRect(graphic->renderer, &rect);
                break;
            case(T_PORTAL):
                SDL_SetRenderDrawColor(graphic->renderer, 0, 128, 128, 0);
                SDL_RenderFillRect(graphic->renderer, &rect);
                break;
            case(T_NOTHING):
                break;
            }
        }
    }
    SDL_Rect rect;
    // Buttons
    rect.x = BUTTON_SPACE;
    rect.y = BUTTON_SPACE;
    rect.w = BUTTON_WIDTH;
    rect.h = BUTTON_HEIGHT;
    for(int i = 0; i < MAX_BUTTONS_COUNT; i++) {
        if(buttons[i].active) {
            SDL_SetRenderDrawColor(
                    graphic->renderer,
                    buttons[i].color.r,
                    buttons[i].color.g,
                    buttons[i].color.b,
                    buttons[i].color.a);
            SDL_RenderFillRect(graphic->renderer, &rect);
            SDL_Color textColor = (SDL_Color){
                .r = 255-buttons[i].color.r,
                .g = 255-buttons[i].color.g,
                .b = 255-buttons[i].color.b,
                .a = 255
            };
            drawText(graphic,
                    buttons[i].text,
                    textColor,
                    rect.x+rect.w/2,
                    rect.y+rect.h/2);
            rect.y += BUTTON_HEIGHT + BUTTON_SPACE;
        }
    }
    rect = (SDL_Rect){ 0 };
    // Cursor
    if(graphic->mouseY2 < graphic->viewport.h+graphic->viewport.y && graphic->mouseX2 < graphic->viewport.w+graphic->viewport.x &&
            graphic->mouseX2 > graphic->viewport.x && graphic->mouseY2 > graphic->viewport.y) {
        SDL_ShowCursor(0);
        rect.x = (((float)graphic->mouseX2/graphic->zoom)*graphic->zoom)+graphic->zoom/5.0;
        rect.y = (((float)graphic->mouseY2/graphic->zoom)*graphic->zoom)+graphic->zoom/5.0;
        rect.w = rect.h = graphic->zoom-(graphic->zoom/2)+1;
        SDL_SetRenderDrawColor(graphic->renderer, 255, 255, 255, 255);
        SDL_RenderFillRect(graphic->renderer, &rect);
    } else SDL_ShowCursor(1);
    // Day/Night indicator
    int r = 0, g = 0, b = 0;
    rect.x = 0;
    rect.y = (graphic->height-PIXEL_SIZE);
    rect.w = graphic->width*PIXEL_SIZE;
    rect.h = PIXEL_SIZE;
    if(w->time > 0) {
        r = 0xEE;
        g = ((float)w->time / (float)w->dayLength) * 255;
    } else {
        g = 128-(((float)-w->time / (float)w->dayLength) * 128);
        b = 255;
    }
    SDL_SetRenderDrawColor(graphic->renderer, r, g, b, 0);
    SDL_RenderFillRect(graphic->renderer, &rect);
    SDL_RenderPresent(graphic->renderer);
}

void keypress(struct World *w, struct Graphic *g) {
    int mx1 = 0, my1 = 0;
    int mx2 = 0, my2 = 0;
    SDL_Event event;
    while (SDL_PollEvent(&event) != 0) {
        switch(event.type) {
        case(SDL_QUIT):
            g->running = false;
            break;
        case(SDL_KEYDOWN):
            switch(event.key.keysym.sym) {
            case(SDLK_SPACE):
                toggleBool(&g->pause);
                break;
            case(SDLK_d):
                g->oneStep = true;
                g->pause = false;
                break;
            case(SDLK_k):
                init(w, w->width, w->height);
                break;
            case(SDLK_r):
                toggleBool(&g->renderActive);
                SDL_ShowCursor(!g->renderActive);
                break;
            case(SDLK_s):
                fprintf(stderr, "Saved world to %s\n", filepath);
                exportWorld(w);
                break;
            case(SDLK_f):
                toggleBool(&g->showTPS);
                break;
            case(SDLK_c):
                g->colorType++;
                if(g->colorType >= C_END) g->colorType = 0;
                break;
            case(SDLK_m):
                g->mouseMode++;
                g->mouseMode %= M_END;
                break;
            case(SDLK_LSHIFT):
                g->mouseX1 = g->mouseX2;
                g->mouseY1 = g->mouseY2;
                g->shiftPressed = true;
                break;
            }
            break;
        case(SDL_KEYUP):
            switch(event.key.keysym.sym) {
            case(SDLK_LSHIFT):
                g->shiftPressed = false;
                break;
            }
            break;
        case(SDL_MOUSEBUTTONDOWN):
            g->mousedown = true;
            g->mouseButton = event.button.button;
            g->mouseX3 = g->mouseX2;
            g->mouseY3 = g->mouseY2;
            break;
        case(SDL_MOUSEBUTTONUP):
            g->mousedown = false;
            break;
        case(SDL_MOUSEMOTION):
            g->mouseX2 = event.button.x;
            g->mouseY2 = event.button.y;
            break;
        case(SDL_MOUSEWHEEL):
            screenToWorld(g, g->mouseX2, g->mouseY2, &mx1, &my1);
            if(event.button.x == 1) {
                g->zoom *= 1.01;
            } else {
                g->zoom *= 0.99;
            }
            screenToWorld(g, g->mouseX2, g->mouseY2, &mx2, &my2);
            g->viewportX += (mx1 - mx2);
            g->viewportY += (my1 - my2);
            break;
        case(SDL_WINDOWEVENT):
            switch(event.window.event) {
            case(SDL_WINDOWEVENT_RESIZED):
                SDL_RenderSetScale(g->renderer, 1, 1);
                g->width = event.window.data1;
                g->height = event.window.data2;
                g->viewport.w = event.window.data1;
                g->viewport.h = event.window.data2-PIXEL_SIZE;
                break;
            }
            break;
        }
    }
    if(g->mousedown) mouseUpdate(w, g);
}

void mouseUpdate(struct World *w, struct Graphic *g) {
    int index;
    int rx, ry;
    for(Button i = 0; i < MAX_BUTTONS_COUNT; i++) {
        if(!buttons[i].active) continue;
        if(checkButton(g, i)) buttons[i].func(g, w);
    }
    if(g->mouseX2 < g->viewport.x || g->mouseX2 > g->viewportX+g->viewport.w) return;
    if(g->mouseY2 < g->viewport.y || g->mouseY2 > g->viewportY+g->viewport.h) return;
    switch(g->mouseMode) {
    case(M_DRAW):
        screenToWorld(g, g->mouseX2, g->mouseY2, &rx, &ry);
        index = cordsToIndex(w, rx, ry);
        if(index < 0 || index > cordsToIndex(w, w->width, w->height)) return;
        switch(g->mouseFunc[g->mouseButton]) {
        case(DM_INFO):
            printInfoAboutCell(w, rx, ry);
            g->mousedown = false;
            break;
        case(DM_WALL):
            w->cells[index].type = T_WALL;
            break;
        case(DM_DEL):
            w->cells[index].type = T_NOTHING;
            break;
        case(DM_FOOD):
            w->cells[index].type = T_FOOD;
            break;
        case(DM_PORTAL):
            w->cells[index].type = T_PORTAL;
            break;
        }
        break;
    case(M_MOVE):
        switch(g->mouseButton) {
        case(SDL_BUTTON_LEFT):
            if(g->mouseX2 <= g->viewport.x)
                SDL_WarpMouseInWindow(g->window, g->viewport.w-1, g->mouseY2);
            else if(g->mouseX2 >= g->viewport.w)
                SDL_WarpMouseInWindow(g->window, g->viewport.x+1, g->mouseY2);

            if(g->mouseY2 <= g->viewport.y)
                SDL_WarpMouseInWindow(g->window, g->mouseX2, g->viewport.h-1);
            else if(g->mouseY2 >= g->viewport.h)
                SDL_WarpMouseInWindow(g->window, g->mouseX2, g->viewport.y+1);

            g->viewportX -= (float)(g->mouseX2 - g->mouseX3) / g->zoom;
            g->viewportY -= (float)(g->mouseY2 - g->mouseY3) / g->zoom;

            g->mouseX3 = g->mouseX2;
            g->mouseY3 = g->mouseY2;
        }
        break;
    case(M_END):
        break;
    }
}

void worldToScreen(struct Graphic *g, int worldX, int worldY, int *screenX, int *screenY) {
    *screenX = (int)((worldX - g->viewportX) * g->zoom)+g->viewport.x;
    *screenY = (int)((worldY - g->viewportY) * g->zoom)+g->viewport.y;
}

void screenToWorld(struct Graphic *g, int screenX, int screenY, int *worldX, int *worldY) {
    *worldX = (int)(((float)(screenX - g->viewport.x) / g->zoom) + g->viewportX);
    *worldY = (int)(((float)(screenY - g->viewport.y) / g->zoom) + g->viewportY);
}
#endif
